<?php
/**
 * 1、获取AccessToken
 * 2、生成token请求参数：
 * appid 数据库分配,唯一,不同端的appid不同
 * nonce 随机值 16位随机值
 * timestamp 当前时间戳
 * sign 签名值,计算方法 参考sign方法
 * type 请求端类型,（'mobile','miniprogram','android','ios','other'）
 * 3、请求接口是需要携带的header头 authorization 加密头规则：USERID base64_encode(appid:token:uid)
 * User: ysongyang
 * Site: https://zz1.com.cn
 * Date: 2019/4/17
 * Time: 14:49
 */

namespace app\api\controller\v1;

use fast\Random;
use think\Controller;
use think\Request;
use app\api\controller\Send;
use app\api\controller\Oauth;
use think\Cache;

class Token extends Controller
{
    use Send;

    /**
     * 请求时间差
     */
    public static $timeDif = 10000;
    public static $accessTokenPrefix = 'accessToken_';
    public static $refreshAccessTokenPrefix = 'refreshAccessToken_';
    /**
     * 过期时间秒数
     * 默认保存24小时,请在客户端保存该信息
     * @var int
     */
    public static $expires = 86400;
    #刷新token过期时间30天
    public static $refreshExpires = 2592000;
    /**
     * 测试appid，正式请数据库进行相关验证
     */
    public static $appid = 'OK5T2r0IFnsO1Ftq';
    /**
     * 测试appsercet
     */
    public static $appsercet = '41l3BdtRbfrVx48VbP5CDGy32tQ0LgRm';


    /**
     * 生成签名
     */
    public function sign()
    {

        echo base64_encode("OK5T2r0IFnsO1Ftq:7JT3obnw591BQmzDpi80R2KeqdhytIj4:1");
        die;

        $nonce = Random::alnum(16);
        $time = time();
        $data = [
            'appsercet' => self::$appsercet,
            'appid' => self::$appid,
            'nonce' => $nonce,
            'timestamp' => $time
        ];

        //按照键名对关联数组进行升序排序
        ksort($data);
        echo $nonce;
        echo "<br/>";
        echo $time;
        echo "<br/>";
        $sign = strtolower(md5(urldecode(http_build_query($data))));
        echo $sign;
        die;

    }


    /**
     * 生成token
     * @param Request $request
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\ModelNotFoundException
     * @throws \think\exception\DbException
     */
    public function token(Request $request)
    {
        //参数验证
        $validate = new \app\api\validate\Token;
        if (!$validate->check(input(''))) {
            return self::returnMsg(401, $validate->getError());
        }
        self::checkParams(input(''));  //参数校验
        //数据库已经有一个用户,这里需要根据input('mobile')去数据库查找有没有这个用户
        //虚拟一个uid返回给调用方
        $userInfo = [];
        try {
            $accessToken = self::setAccessToken(array_merge($userInfo, input('')));  //传入参数应该是根据手机号查询改用户的数据
            return self::returnMsg(200, 'success', $accessToken);
        } catch (Exception $e) {
            return self::returnMsg(500, 'fail', $e);
        }
    }

    /**
     * 刷新token
     */
    public function refresh()
    {
        $refresh_token = input('post.refresh_token');
        $appid = input('post.appid');
        if (!$refresh_token || !$appid) {
            return self::returnMsg(401, 'refresh_token或appid参数错误！');
        }
        #查看刷新token是否存在
        $cache_refresh_token = Cache::get(self::$refreshAccessTokenPrefix . $appid);
        if (!$cache_refresh_token) {
            return self::returnMsg(401, 'fail', 'refresh_token is null');
        } else {
            if ($cache_refresh_token !== $refresh_token) {
                return self::returnMsg(401, 'fail', 'refresh_token is error');
            } else {
                #重新给用户生成调用token
                $data['appid'] = $appid;
                $accessToken = self::setAccessToken($data);
                return self::returnMsg(200, 'success', $accessToken);
            }
        }
    }


    /**
     * 参数检测
     * @param array $params
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\ModelNotFoundException
     * @throws \think\exception\DbException
     */
    public static function checkParams($params = [])
    {
        //时间戳校验
        if (abs($params['timestamp'] - time()) > self::$timeDif) {
            return self::returnMsg(401, '请求时间戳与服务器时间戳异常', 'timestamp：' . time());
        }
        //appid检测，查找数据库或者redis进行验证
        $oauthInfo = \app\api\model\Oauth::checkAppId($params);
        if ($oauthInfo == false) {
            return self::returnMsg(401, 'appid 错误');
        }
        //签名检测
        $sign = Oauth::makeSign($params, $oauthInfo['app_secret']);
        if ($sign !== $params['sign']) {
            return self::returnMsg(401, 'sign错误', 'sign：' . $sign);
        }
    }


    /**
     * 设置AccessToken
     * @param $clientInfo
     * @return array
     */
    protected function setAccessToken($clientInfo)
    {
        //生成令牌
        $accessToken = self::buildAccessToken();
        $refresh_token = self::getRefreshToken($clientInfo['appid']);
        $accessTokenInfo = [
            'access_token' => $accessToken,//访问令牌
            'expires_time' => time() + self::$expires,      //过期时间时间戳
            'refresh_token' => $refresh_token,//刷新的token
            'refresh_expires_time' => time() + self::$refreshExpires,      //过期时间时间戳
            'expires_in' => self::$expires, //有效期
            'client' => $clientInfo,//用户信息
        ];
        self::saveAccessToken($accessToken, $accessTokenInfo);  //保存本次token
        self::saveRefreshToken($refresh_token, $clientInfo['appid']);
        return $accessTokenInfo;
    }

    /**
     * 刷新用的token检测是否还有效
     * @param string $appid
     * @return bool|mixed|string
     */
    public static function getRefreshToken($appid = '')
    {
        return Cache::get(self::$refreshAccessTokenPrefix . $appid) ? Cache::get(self::$refreshAccessTokenPrefix . $appid) : self::buildAccessToken();
    }

    /**
     * 生成AccessToken
     * @param int $lenght
     * @return bool|string
     */
    protected static function buildAccessToken($lenght = 32)
    {
        //生成AccessToken
        $str_pol = "1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ123456789abcdefghijklmnopqrstuvwxyz";
        return substr(str_shuffle($str_pol), 0, $lenght);

    }

    /**
     * 存储
     * @param $accessToken
     * @param $accessTokenInfo
     */
    protected static function saveAccessToken($accessToken, $accessTokenInfo)
    {
        //存储accessToken
        Cache::set(Oauth::$accessTokenPrefix . $accessToken, $accessTokenInfo, self::$expires);

        //存储用户与信息索引 用于比较,这里涉及到user_id，如果有需要请关掉注释
        //Cache::set(self::$accessTokenAndClientPrefix . $accessTokenInfo['client']['user_id'], $accessToken, self::$expires);
    }


    /**
     * 刷新token存储
     * @param $refresh_token
     * @param $appid
     */
    protected static function saveRefreshToken($refresh_token, $appid)
    {
        //存储RefreshToken
        cache(self::$refreshAccessTokenPrefix . $appid, $refresh_token, self::$refreshExpires);
    }
}